package com.sulent.frame.file.utils;

import com.sulent.frame.file.dto.DirTree;
import com.sulent.frame.file.dto.FileVo;
import io.minio.*;
import io.minio.errors.*;
import io.minio.messages.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.apache.commons.lang3.StringUtils;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;

/**
 * @ClassName MinioUtils
 * @Description: 基于MinioClient封装的工具类，
 * 参考：
 * https://min.io/docs/minio/linux/developers/java/API.html、
 * https://minio-java.min.io/
 * @Author Submerge.
 * @Since 2024/1/19 11:39
 * @Version 1.0
 */
@Data
public class MinioUtils {


    /**
     * 连接minio
     *
     * @param endpoint
     * @param accessKey
     * @param secretKey
     * @return
     */
    public static MinioClient connectionMinio(String endpoint, String accessKey, String secretKey) {
        try {
            return MinioClient
                    .builder()
                    .endpoint(endpoint)
                    .credentials(accessKey, secretKey)
                    .build();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Minio连接异常！");
        }
    }

    /**
     * 文件目录列表
     *
     * @param minioClient
     * @param bucketName
     * @param filePath
     * @return
     */
    public static List<FileVo> listFiles(MinioClient minioClient, String bucketName, String filePath) {
        List<FileVo> fileVos = new ArrayList<>();
        ListObjectsArgs args = null;
        if (StringUtils.isNotBlank(filePath)) {
            if (!filePath.endsWith("/")){
                filePath= filePath+"/";
            }
            args = ListObjectsArgs
                    .builder()
                    .bucket(bucketName)
                    .prefix(filePath).maxKeys(2)
                    .build();
        } else {
            args = ListObjectsArgs
                    .builder()
                    .bucket(bucketName).maxKeys(2)
                    .build();
        }
        try {
            Iterable<Result<Item>> results = minioClient.listObjects(args);
            for (Result<Item> next : results) {
                Item item = next.get();
                FileVo fileVo = new FileVo();
                fileVo.setName(item.objectName());
                fileVo.setSize(item.size());
                fileVo.setType(item.isDir() ? "Directory" : "File");
                fileVos.add(fileVo);
            }
            return fileVos;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Minio列出文件列表失败！");
        } finally {
            try {
                minioClient.traceOff();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    public static List<FileVo> pageFiles(MinioClient minioClient, String bucketName, String filePath,String startAfter) {
        List<FileVo> fileVos = new ArrayList<>();


        ListObjectsArgs args =ListObjectsArgs.builder()
                .bucket(bucketName)
                .startAfter(startAfter)
                .prefix(filePath)
                .maxKeys(5)
                .build();

        try {
            Iterable<Result<Item>> results = minioClient.listObjects(args);

            for (Result<Item> next : results) {
                Item item = next.get();
                FileVo fileVo = new FileVo();
                fileVo.setName(item.objectName());
                fileVo.setSize(item.size());
                fileVo.setType(item.isDir() ? "Directory" : "File");
                fileVos.add(fileVo);
            }
            return fileVos;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Minio列出文件列表失败！");
        } finally {
            try {
                minioClient.traceOff();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * 组成对象：通过使用服务器端复制来组合来自不同源对象的数据来创建对象。
     *ComposeObjectArgs
     * @param minioClient
     */
    public void composeObject(MinioClient minioClient) throws Exception {
        List<ComposeSource> sourceObjectList = new ArrayList<ComposeSource>();
        sourceObjectList.add(
                ComposeSource.builder().bucket("my-job-bucket").object("my-objectname-part-one").build());
        sourceObjectList.add(
                ComposeSource.builder().bucket("my-job-bucket").object("my-objectname-part-two").build());
        sourceObjectList.add(
                ComposeSource.builder().bucket("my-job-bucket").object("my-objectname-part-three").build());

        // Create my-bucketname/my-objectname by combining source object list.
        minioClient.composeObject(
                ComposeObjectArgs.builder()
                        .bucket("my-bucketname")
                        .object("my-objectname")
                        .sources(sourceObjectList)
                        .build());
    }

    /**
     * 复制对象：通过服务器端从另一个对象复制数据来创建对象。
     *CopyObjectArgs
     * @param minioClient
     * @throws Exception
     */
    public void copyObject(MinioClient minioClient) throws Exception {
        // Create object "my-objectname" in bucket "my-bucketname" by copying from object
        // "my-objectname" in bucket "my-source-bucketname".
        minioClient.copyObject(
                CopyObjectArgs.builder()
                        .bucket("my-bucketname")
                        .object("my-objectname")
                        .source(
                                CopySource.builder()
                                        .bucket("my-source-bucketname")
                                        .object("my-objectname")
                                        .build())
                        .build());

    }

    /**
     * 删除对象的标签
     *
     * @param minioClient
     * @throws Exception
     */
    public void deleteObjectTags(MinioClient minioClient) throws Exception {
        minioClient.deleteObjectTags(
                DeleteObjectTagsArgs.builder().bucket("my-bucketname").object("my-objectname").build());
    }

    /**
     * 获取对象的数据。使用后必须关闭返回的网络资源才能释放网络资源。
     * GetObjectArgs
     * @param minioClient
     * @throws Exception
     */
    public void getObject(MinioClient minioClient) throws Exception {
        // get object given the bucket and object name
        try (InputStream stream = minioClient.getObject(
                GetObjectArgs.builder()
                        .bucket("my-bucketname")
                        .object("my-objectname")
                        .build())) {
            // Read data from stream
        }
/*
// get object data from offset 指定偏移量开始
        try (InputStream stream = minioClient.getObject(
                GetObjectArgs.builder()
                        .bucket("my-bucketname")
                        .object("my-objectname")
                        .offset(1024L)
                        .build())) {
            // Read data from stream
        }

// get object data from offset to length 指定偏移量开始到指定长度结束
        try (InputStream stream = minioClient.getObject(
                GetObjectArgs.builder()
                        .bucket("my-bucketname")
                        .object("my-objectname")
                        .offset(1024L)
                        .length(4096L)
                        .build())) {
            // Read data from stream
        }*/
    }

    /**
     * 将对象的数据下载到文件。
     * DownloadObjectArgs
     * @param minioClient
     * @throws Exception
     */
    public void downloadObject(MinioClient minioClient) throws Exception{
// Download object given the bucket, object name and output file name
        minioClient.downloadObject(
                DownloadObjectArgs.builder()
                        .bucket("my-bucketname")
                        .object("my-objectname")
                        .filename("my-object-file")
                        .build());
    }

    /**
     * 将给定的流作为存储桶中的对象上传。
     * PutObjectArgs
     * @param minioClient
     * @param inputStream
     * @throws Exception
     */
    public static void putObject(MinioClient minioClient,InputStream inputStream) throws Exception{
        // Upload known sized input stream.
        ObjectWriteResponse objectWriteResponse = minioClient.putObject(
                PutObjectArgs.builder().bucket("ai-model").object("my-objectname").stream(
                                inputStream, inputStream.available(), -1)//partSize传入-1，默认按照5MB进行分割
                        .contentType("video/mp4")
                        .build());

/*        // Upload unknown sized input stream.
        minioClient.putObject(
                PutObjectArgs.builder().bucket("my-bucketname").object("my-objectname").stream(
                                inputStream, -1, 10485760)
                        .contentType("video/mp4")
                        .build());

        // Create object ends with '/' (also called as folder or directory).
        minioClient.putObject(
                PutObjectArgs.builder().bucket("my-bucketname").object("path/to/").stream(
                                new ByteArrayInputStream(new byte[] {}), 0, -1)
                        .build());

        // Upload input stream with headers and user metadata.
        Map<String, String> headers = new HashMap<>();
        headers.put("X-Amz-Storage-Class", "REDUCED_REDUNDANCY");
        Map<String, String> userMetadata = new HashMap<>();
        userMetadata.put("My-Project", "Project One");
        minioClient.putObject(
                PutObjectArgs.builder().bucket("my-bucketname").object("my-objectname").stream(
                                inputStream, inputStream.available(), -1)
                        .headers(headers)
                        .userMetadata(userMetadata)
                        .build());*/
    }


    /**
     * 将文件中的内容作为存储桶中的对象上传。
     * UploadObjectArgs
     * @param minioClient
     * @throws Exception
     */
    public void uploadObject(MinioClient minioClient) throws Exception{
        // Upload an JSON file.
        minioClient.uploadObject(
                UploadObjectArgs.builder()
                        .bucket("my-bucketname").object("my-objectname").filename("person.json").build());
        /*
        // Upload a video file.
        minioClient.uploadObject(
                UploadObjectArgs.builder()
                        .bucket("my-bucketname")
                        .object("my-objectname")
                        .filename("my-video.avi")
                        .contentType("video/mp4")
                        .build());*/
    }


    /**
     * 压缩上传：在单个 put 调用中上传多个对象。这是通过创建中间 TAR 文件来完成的，该文件可以选择性地压缩，该文件将上传到 S3 服务。
     * UploadSnowballObjectsArgs
     * @param minioClient
     * @throws Exception
     */
    public void uploadSnowballObjects(MinioClient minioClient) throws Exception{
        List<SnowballObject> objects = new ArrayList<SnowballObject>();
        objects.add(
                new SnowballObject(
                        "my-object-one",
                        new ByteArrayInputStream("hello".getBytes(StandardCharsets.UTF_8)),
                        5,
                        null));
        objects.add(
                new SnowballObject(
                        "my-object-two",
                        new ByteArrayInputStream("java".getBytes(StandardCharsets.UTF_8)),
                        4,
                        null));
        minioClient.uploadSnowballObjects(
                UploadSnowballObjectsArgs.builder().bucket("my-bucketname").objects(objects).build());
    }


    /**
     * 删除对象
     * RemoveObjectArgs
     * @param minioClient
     * @throws Exception
     */
    public void removeObject(MinioClient minioClient) throws Exception{
        // Remove object.
        minioClient.removeObject(
                RemoveObjectArgs.builder().bucket("my-bucketname").object("my-objectname").build());

/*        // Remove versioned object.
        minioClient.removeObject(
                RemoveObjectArgs.builder()
                        .bucket("my-bucketname")
                        .object("my-versioned-objectname")
                        .versionId("my-versionid")
                        .build());

        // Remove versioned object bypassing Governance mode.
        minioClient.removeObject(
                RemoveObjectArgs.builder()
                        .bucket("my-bucketname")
                        .object("my-versioned-objectname")
                        .versionId("my-versionid")
                        .bypassRetentionMode(true)
                        .build()); */
    }

    /**
     * 删除多个对象
     * DeleteObject
     * @param minioClient
     * @throws Exception
     */
    public void removeObjects(MinioClient minioClient) throws Exception{
        minioClient.traceOff();
        List<DeleteObject> objects = new LinkedList<>();
        objects.add(new DeleteObject("my-objectname1"));
        objects.add(new DeleteObject("my-objectname2"));
        objects.add(new DeleteObject("my-objectname3"));
        Iterable<Result<DeleteError>> results =
                minioClient.removeObjects(
                        RemoveObjectsArgs.builder().bucket("my-bucketname").objects(objects).build());
        for (Result<DeleteError> result : results) {
            DeleteError error = result.get();
            System.out.println(
                    "Error in deleting object " + error.objectName() + "; " + error.message());
        }


    }


    /**
     * 恢复对象
     * RestoreObjectArgs
     * @param minioClient
     * @throws Exception
     */
    public void restoreObject(MinioClient minioClient) throws Exception{
/*        // Restore object.
        minioClient.restoreObject(
                RestoreObjectArgs.builder()
                        .bucket("my-bucketname")
                        .object("my-objectname")
                        .request(new RestoreRequest(null, null, null, null, null, null))
                        .build());

        // Restore versioned object.
        minioClient.restoreObject(
                RestoreObjectArgs.builder()
                        .bucket("my-bucketname")
                        .object("my-versioned-objectname")
                        .versionId("my-versionid")
                        .request(new RestoreRequest(null, null, null, null, null, null))
                        .build());*/
    }


    /**
     * 通过SQL表达式选择对象的内容。
     * @param minioClient
     * @throws Exception
     */
    public void selectObjectContent(MinioClient minioClient) throws Exception{

        String sqlExpression = "select * from S3Object";
        InputSerialization is = new InputSerialization(null, false, null, null, FileHeaderInfo.USE, null, null, null);
        OutputSerialization os = new OutputSerialization(null, null, null, QuoteFields.ASNEEDED, null);
        SelectResponseStream stream =
                minioClient.selectObjectContent(
                        SelectObjectContentArgs.builder()
                                .bucket("my-bucketname")
                                .object("my-objectName")
                                .sqlExpression(sqlExpression)
                                .inputSerialization(is)
                                .outputSerialization(os)
                                .requestProgress(true)
                                .build());

        byte[] buf = new byte[512];
        int bytesRead = stream.read(buf, 0, buf.length);
        System.out.println(new String(buf, 0, bytesRead, StandardCharsets.UTF_8));

        Stats stats = stream.stats();
        System.out.println("bytes scanned: " + stats.bytesScanned());
        System.out.println("bytes processed: " + stats.bytesProcessed());
        System.out.println("bytes returned: " + stats.bytesReturned());

        stream.close();

    }

    /**
     * 获取对象的对象信息和元数据。
     * @param minioClient
     * @throws Exception
     */
    public void statObject(MinioClient minioClient) throws Exception{
        // Get information of an object.
        StatObjectResponse statObjectResponse = minioClient.statObject(
                StatObjectArgs.builder().bucket("my-bucketname").object("my-objectname").build());
    }


    public static List<DirTree> getDirTreeByPath(MinioClient minioClient,String path, Integer level) {
        List<DirTree> dirTrees = new ArrayList<>();
        try {
            if (level == 0 ) return dirTrees;
            Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder().bucket("datacenter").prefix(path).delimiter("/").build());
            for (Result<Item> next : results) {
                Item item = next.get();
                if (item.isDir()){
                    DirTree dirTree = new DirTree();
                    dirTree.setName(item.objectName());
                    dirTree.setChildren(getDirTreeByPath(minioClient,item.objectName(),level-1));
                    dirTrees.add(dirTree);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("minio获取文件目录树失败："+e.getMessage(),e);
        }
        return dirTrees;
    }


    public static boolean objectExist(MinioClient minioClient, String bucketName, String objectName) throws MinioException, InvalidKeyException, NoSuchAlgorithmException, IOException {
        try {
            // 尝试获取对象信息
            minioClient.statObject(
                    StatObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .build()
            );
            return true; // 对象存在
        } catch (ErrorResponseException e) {
            // 对象不存在时，ErrorResponseException 会被抛出
            return false;
        }
    }



}
